// renderer.cpp
//

#include "renderer.hpp"
#include "scene.hpp"



// Renderer::register_program
//
ProgramID Renderer::register_program ( std::string name,
                                       std::string vertex_shader,
                                       std::string fragment_shader )
{
  Program prog;
  prog.id = registered_programs.size();
  prog.name = name;
  prog.vertex_shader = vertex_shader;
  prog.fragment_shader = fragment_shader;
  registered_programs.emplace_back(prog);
  return prog.id;
}



// Renderer::register_pipeline
//
PipelineID Renderer::register_pipeline  ( std::string name,
                                          ProgramID program )
{
  Pipeline def;
  def.id = registered_pipelines.size();
  def.name = name;
  def.program = program;
  registered_pipelines.emplace_back(def);
  return def.id;
}



// Renderer::register_render_mode
//
RenderModeID Renderer::register_render_mode ( std::string name,
                                              const std::vector<PipelineBinding> &pipeline_bindings )
{
  RenderMode def;
  def.id = registered_render_modes.size();
  def.name = name;

  for (const auto &it: pipeline_bindings)
    {
      if (def.pipelines.size() <= it.entity_family)
        def.pipelines.resize(it.entity_family+1);
      def.pipelines[it.entity_family] = it.pipeline;
    }
  
  registered_render_modes.emplace_back(def);
  return def.id;
}



// Renderer::get_pipeline_id
//
PipelineID Renderer::get_pipeline_id ( const std::string &name )
{
  for (const auto &pipeline: registered_pipelines)
    {
      if (pipeline.name == name)
        return pipeline.id;
    }
  GLT_FATAL("pipeline not found: '%s'", name.c_str());
  return 0;
}



// Renderer::setup_pipeline
//
void Renderer::setup_pipeline ( PipelineID pipeline_id )
{
  if (pipeline_id == current_pipeline)
    return;

  GLT_TRACE("setup pipeline: %lu", pipeline_id);

  Pipeline &pipeline = registered_pipelines.at(pipeline_id);
  use_program(pipeline.program);
  
  current_pipeline = pipeline_id;
}



// Renderer::use_program
//
void Renderer::use_program ( ProgramID program_id )
{
  if (program_id == current_program)
    return;

  GLT_TRACE("use program: %lu", program_id);

  Program &program = registered_programs.at(program_id);
  if (!program.loaded)
    {
      wgl::Shader vert_shader{GL_VERTEX_SHADER, program.vertex_shader};
      wgl::Shader frag_shader{GL_FRAGMENT_SHADER, program.fragment_shader};
      program.wgl_program.attach_shader(vert_shader);
      program.wgl_program.attach_shader(frag_shader);
      program.wgl_program.link();
      program.m4_projection.init(program.wgl_program, "m4_projection");
      program.m4_model.init(program.wgl_program, "m4_model");
    }

  program.m4_projection.setm4(camera.matrix());
  
  current_program = program_id;
}



// Renderer::render
//
void Renderer::render ( Scene &scene )
{
  GLT_TRACE("Renderer.render()");

  GLT_ASSERT(render_mode_id < registered_render_modes.size());
  RenderMode &render_mode = registered_render_modes[render_mode_id];

  for (auto entity: scene)
    {
      PipelineID pipeline_id = render_mode.pipelines[entity->get_family()];

      GLT_TRACE("  entity %p: family=%lu, pipeline=%lu", entity, entity->get_family(), pipeline_id);
      setup_pipeline(pipeline_id);

      Pipeline &pipeline = registered_pipelines.at(pipeline_id);
      Program &program = registered_programs.at(pipeline.program);
      program.m4_model.setm4(entity->matrix());
    }
}
